/*
 * Copyright (C) 2017 Jared Rummler
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jaredrummler.colorpicker;
//ps-like custom 2-dimensional color picker view with a hue bar
//I love it.
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ComposeShader;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;

import com.jaredrummler.android.colorpicker.R;

/**
 * Displays a color picker to the user and allow them to select a color. A slider for the alpha channel is also available.
 * Enable it by setting setAlphaSliderVisible(boolean) to true.
 */
public class ColorPickerView extends View {

	private final static int DEFAULT_BORDER_COLOR = 0xFF6E6E6E;
	private final static int DEFAULT_SLIDER_COLOR = 0xFFBDBDBD;

	private final static int HUE_PANEL_WDITH_DP = 30;
	private final static int ALPHA_PANEL_HEIGH_DP = 20;
	private final static int PANEL_SPACING_DP = 10;
	private final static int CIRCLE_TRACKER_RADIUS_DP = 5;
	private final static int SLIDER_TRACKER_SIZE_DP = 4;
	private final static int SLIDER_TRACKER_OFFSET_DP = 2;

	/**
	 * The width in pixels of the border
	 * surrounding all color panels.
	 */
	private final static int BORDER_WIDTH_PX = 1;

	/**
	 * The width in px of the hue panel.
	 */
	private int huePanelWidthPx;
	/**
	 * The height in px of the alpha panel
	 */
	private int alphaPanelHeightPx;
	/**
	 * The distance in px between the different
	 * color panels.
	 */
	private int panelSpacingPx;
	/**
	 * The radius in px of the color palette tracker circle.
	 */
	private int circleTrackerRadiusPx;
	/**
	 * The px which the tracker of the hue or alpha panel
	 * will extend outside of its bounds.
	 */
	private int sliderTrackerOffsetPx;
	/**
	 * Height of slider tracker on hue panel,
	 * width of slider on alpha panel.
	 */
	private int sliderTrackerSizePx;

	private Paint satValPaint;
	private Paint satValTrackerPaint;

	private Paint alphaPaint;
	private Paint alphaTextPaint;
	private Paint hueAlphaTrackerPaint;

	private Paint borderPaint;

	private Shader valShader;
	private Shader satShader;
	private Shader alphaShader;

	/*
	 * We cache a bitmap of the sat/val panel which is expensive to draw each time.
	 * We can reuse it when the user is sliding the circle picker as long as the hue isn't changed.
	 */
	private BitmapCache satValBackgroundCache;
	/* We cache the hue background to since its also very expensive now. */
	private BitmapCache hueBackgroundCache;

	/* Current values */
	private int alpha = 0xff;
	private float hue = 360f;
	private float sat = 0f;
	private float val = 0f;

	private int sliderTrackerColor = DEFAULT_SLIDER_COLOR;
	private int borderColor = DEFAULT_BORDER_COLOR;

	/**
	 * Minimum required padding. The offset from the
	 * edge we must have or else the finger tracker will
	 * get clipped when it's drawn outside of the view.
	 */
	private int mRequiredPadding;

	/**
	 * The Rect in which we are allowed to draw.
	 * Trackers can extend outside slightly,
	 * due to the required padding we have set.
	 */
	private Rect drawingRect;

	private Rect satValRect;
	private Rect hueRect;
	private Rect alphaRect;

	private Point startTouchPoint = null;

	private AlphaPatternDrawable alphaPatternDrawable;
	private OnColorChangedListener onColorChangedListener;

	public ColorPickerView(Context context) {
		this(context, null);
	}

	public ColorPickerView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public ColorPickerView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init(context, attrs);
	}

	@Override public Parcelable onSaveInstanceState() {
		Bundle state = new Bundle();
		state.putParcelable("instanceState", super.onSaveInstanceState());
		state.putInt("alpha", alpha);
		state.putFloat("hue", hue);
		state.putFloat("sat", sat);
		state.putFloat("val", val);
		return state;
	}

	@Override public void onRestoreInstanceState(Parcelable state) {
		if (state instanceof Bundle) {
			Bundle bundle = (Bundle) state;
			alpha = bundle.getInt("alpha");
			hue = bundle.getFloat("hue");
			sat = bundle.getFloat("sat");
			val = bundle.getFloat("val");
			state = bundle.getParcelable("instanceState");
		}
		super.onRestoreInstanceState(state);
	}

	private void init(Context context, AttributeSet attrs) {
		//Load those if set in xml resource file.
		TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPickerView);
		sliderTrackerColor = a.getColor(R.styleable.ColorPickerView_cpv_sliderColor, 0xFFBDBDBD);
		borderColor = a.getColor(R.styleable.ColorPickerView_cpv_borderColor, 0xFF6E6E6E);
		a.recycle();

		applyThemeColors(context);

		huePanelWidthPx = DrawingUtils.dpToPx(getContext(), HUE_PANEL_WDITH_DP);
		alphaPanelHeightPx = DrawingUtils.dpToPx(getContext(), ALPHA_PANEL_HEIGH_DP);
		panelSpacingPx = DrawingUtils.dpToPx(getContext(), PANEL_SPACING_DP);
		circleTrackerRadiusPx = DrawingUtils.dpToPx(getContext(), CIRCLE_TRACKER_RADIUS_DP);
		sliderTrackerSizePx = DrawingUtils.dpToPx(getContext(), SLIDER_TRACKER_SIZE_DP);
		sliderTrackerOffsetPx = DrawingUtils.dpToPx(getContext(), SLIDER_TRACKER_OFFSET_DP);

		mRequiredPadding = getResources().getDimensionPixelSize(R.dimen.cpv_required_padding);

		initPaintTools();

		//Needed for receiving trackball motion events.
		setFocusable(false);
		setFocusableInTouchMode(true);
	}

	private void applyThemeColors(Context c) {
		// If no specific border/slider color has been
		// set we take the default secondary text color
		// as border/slider color. Thus it will adopt
		// to theme changes automatically.

		final TypedValue value = new TypedValue();
		TypedArray a = c.obtainStyledAttributes(value.data, new int[]{android.R.attr.textColorSecondary});

		if (borderColor == DEFAULT_BORDER_COLOR) {
			borderColor = a.getColor(0, DEFAULT_BORDER_COLOR);
		}

		if (sliderTrackerColor == DEFAULT_SLIDER_COLOR) {
			sliderTrackerColor = a.getColor(0, DEFAULT_SLIDER_COLOR);
		}

		a.recycle();
	}

	private void initPaintTools() {

		satValPaint = new Paint();
		satValTrackerPaint = new Paint();
		hueAlphaTrackerPaint = new Paint();
		alphaPaint = new Paint();
		alphaTextPaint = new Paint();
		borderPaint = new Paint();

		satValTrackerPaint.setStyle(Style.STROKE);
		satValTrackerPaint.setStrokeWidth(DrawingUtils.dpToPx(getContext(), 2));
		satValTrackerPaint.setAntiAlias(true);

		hueAlphaTrackerPaint.setColor(sliderTrackerColor);
		hueAlphaTrackerPaint.setStyle(Style.STROKE);
		hueAlphaTrackerPaint.setStrokeWidth(DrawingUtils.dpToPx(getContext(), 2));
		hueAlphaTrackerPaint.setAntiAlias(true);

		alphaTextPaint.setColor(0xff1c1c1c);
		alphaTextPaint.setTextSize(DrawingUtils.dpToPx(getContext(), 14));
		alphaTextPaint.setAntiAlias(true);
		alphaTextPaint.setTextAlign(Align.CENTER);
		alphaTextPaint.setFakeBoldText(true);

	}

	@Override protected void onDraw(Canvas canvas) {
		if (drawingRect.width() <= 0 || drawingRect.height() <= 0) {
			return;
		}

		drawSatValPanel(canvas);
		drawHuePanel(canvas);
		drawAlphaPanel(canvas);
	}

	private void drawSatValPanel(Canvas canvas) {
		final Rect rect = satValRect;

		if (BORDER_WIDTH_PX > 0) {
			borderPaint.setColor(borderColor);
			canvas.drawRect(drawingRect.left, drawingRect.top,
					rect.right + BORDER_WIDTH_PX,
					rect.bottom + BORDER_WIDTH_PX, borderPaint);
		}

		if (valShader == null) {
			//Black gradient has either not been created or the view has been resized.
			valShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, 0xffffffff, 0xff000000, TileMode.CLAMP);
		}

		//If the hue has changed we need to recreate the cache.
		if (satValBackgroundCache == null || satValBackgroundCache.value != hue) {

			if (satValBackgroundCache == null) {
				satValBackgroundCache = new BitmapCache();
			}

			//We create our bitmap in the cache if it doesn't exist.
			if (satValBackgroundCache.bitmap == null) {
				satValBackgroundCache.bitmap = Bitmap
						.createBitmap(rect.width(), rect.height(), Config.ARGB_8888);
			}

			//We create the canvas once so we can draw on our bitmap and the hold on to it.
			if (satValBackgroundCache.canvas == null) {
				satValBackgroundCache.canvas = new Canvas(satValBackgroundCache.bitmap);
			}

			int rgb = Color.HSVToColor(new float[]{hue, 1f, 1f});

			satShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, 0xffffffff, rgb, TileMode.CLAMP);

			ComposeShader mShader = new ComposeShader(
					valShader, satShader, PorterDuff.Mode.MULTIPLY);
			satValPaint.setShader(mShader);

			// Finally we draw on our canvas, the result will be
			// stored in our bitmap which is already in the cache.
			// Since this is drawn on a canvas not rendered on
			// screen it will automatically not be using the
			// hardware acceleration. And this was the code that
			// wasn't supported by hardware acceleration which mean
			// there is no need to turn it of anymore. The rest of
			// the view will still be hw accelerated.
			satValBackgroundCache.canvas.drawRect(0, 0,
					satValBackgroundCache.bitmap.getWidth(),
					satValBackgroundCache.bitmap.getHeight(),
					satValPaint);

			//We set the hue value in our cache to which hue it was drawn with,
			//then we know that if it hasn't changed we can reuse our cached bitmap.
			satValBackgroundCache.value = hue;

		}

		// We draw our bitmap from the cached, if the hue has changed
		// then it was just recreated otherwise the old one will be used.
		canvas.drawBitmap(satValBackgroundCache.bitmap, null, rect, null);

		Point p = satValToPoint(sat, val);

		satValTrackerPaint.setColor(0xff000000);
		canvas.drawCircle(p.x, p.y, circleTrackerRadiusPx - DrawingUtils.dpToPx(getContext(), 1), satValTrackerPaint);

		satValTrackerPaint.setColor(0xffdddddd);
		canvas.drawCircle(p.x, p.y, circleTrackerRadiusPx, satValTrackerPaint);

	}

	private void drawHuePanel(Canvas canvas) {
		final Rect rect = hueRect;

		if (BORDER_WIDTH_PX > 0) {
			borderPaint.setColor(borderColor);

			canvas.drawRect(rect.left - BORDER_WIDTH_PX,
					rect.top - BORDER_WIDTH_PX,
					rect.right + BORDER_WIDTH_PX,
					rect.bottom + BORDER_WIDTH_PX,
					borderPaint);
		}

		if (hueBackgroundCache == null) {
			hueBackgroundCache = new BitmapCache();
			hueBackgroundCache.bitmap = Bitmap.createBitmap(rect.width(), rect.height(), Config.ARGB_8888);
			hueBackgroundCache.canvas = new Canvas(hueBackgroundCache.bitmap);

			int[] hueColors = new int[(int) (rect.height() + 0.5f)];

			// Generate array of all colors, will be drawn as individual lines.
			float h = 360f;
			for (int i = 0; i < hueColors.length; i++) {
				hueColors[i] = Color.HSVToColor(new float[]{h, 1f, 1f});
				h -= 360f / hueColors.length;
			}

			// Time to draw the hue color gradient,
			// its drawn as individual lines which
			// will be quite many when the resolution is high
			// and/or the panel is large.
			Paint linePaint = new Paint();
			linePaint.setStrokeWidth(0);
			for (int i = 0; i < hueColors.length; i++) {
				linePaint.setColor(hueColors[i]);
				hueBackgroundCache.canvas.drawLine(0, i, hueBackgroundCache.bitmap.getWidth(), i, linePaint);
			}
		}

		canvas.drawBitmap(hueBackgroundCache.bitmap, null, rect, null);

		Point p = hueToPoint(hue);

		RectF r = new RectF();
		r.left = rect.left - sliderTrackerOffsetPx;
		r.right = rect.right + sliderTrackerOffsetPx;
		r.top = p.y - (sliderTrackerSizePx / 2);
		r.bottom = p.y + (sliderTrackerSizePx / 2);

		canvas.drawRoundRect(r, 2, 2, hueAlphaTrackerPaint);
	}

	private void drawAlphaPanel(Canvas canvas) {
		/*
		 * Will be drawn with hw acceleration, very fast.
		 * Also the AlphaPatternDrawable is backed by a bitmap
		 * generated only once if the size does not change.
		 */
		if (alphaRect == null || alphaPatternDrawable == null) return;

		final Rect rect = alphaRect;

		if (BORDER_WIDTH_PX > 0) {
			borderPaint.setColor(borderColor);
			canvas.drawRect(rect.left - BORDER_WIDTH_PX,
					rect.top - BORDER_WIDTH_PX,
					rect.right + BORDER_WIDTH_PX,
					rect.bottom + BORDER_WIDTH_PX,
					borderPaint);
		}

		alphaPatternDrawable.draw(canvas);

		float[] hsv = new float[]{hue, sat, val};
		int color = Color.HSVToColor(hsv);
		int acolor = Color.HSVToColor(0, hsv);

		alphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
				color, acolor, TileMode.CLAMP);

		alphaPaint.setShader(alphaShader);

		canvas.drawRect(rect, alphaPaint);

		//if (alphaSliderText != null && !alphaSliderText.equals(""))
		//	canvas.drawText(alphaSliderText, rect.centerX(), rect.centerY() + DrawingUtils.dpToPx(getContext(), 4), alphaTextPaint);

		Point p = alphaToPoint(alpha);

		RectF r = new RectF();
		r.left = p.x - (sliderTrackerSizePx / 2);
		r.right = p.x + (sliderTrackerSizePx / 2);
		r.top = rect.top - sliderTrackerOffsetPx;
		r.bottom = rect.bottom + sliderTrackerOffsetPx;

		canvas.drawRoundRect(r, 2, 2, hueAlphaTrackerPaint);
	}

	private Point hueToPoint(float hue) {

		final Rect rect = hueRect;
		final float height = rect.height();

		Point p = new Point();

		p.y = (int) (height - (hue * height / 360f) + rect.top);
		p.x = rect.left;

		return p;
	}

	private Point satValToPoint(float sat, float val) {

		final Rect rect = satValRect;
		final float height = rect.height();
		final float width = rect.width();

		Point p = new Point();

		p.x = (int) (sat * width + rect.left);
		p.y = (int) ((1f - val) * height + rect.top);

		return p;
	}

	private Point alphaToPoint(int alpha) {

		final Rect rect = alphaRect;
		final float width = rect.width();

		Point p = new Point();

		p.x = (int) (width - (alpha * width / 0xff) + rect.left);
		p.y = rect.top;

		return p;

	}

	private float[] pointToSatVal(float x, float y) {

		final Rect rect = satValRect;
		float[] result = new float[2];

		float width = rect.width();
		float height = rect.height();

		if (x < rect.left) {
			x = 0f;
		} else if (x > rect.right) {
			x = width;
		} else {
			x = x - rect.left;
		}

		if (y < rect.top) {
			y = 0f;
		} else if (y > rect.bottom) {
			y = height;
		} else {
			y = y - rect.top;
		}

		result[0] = 1.f / width * x;
		result[1] = 1.f - (1.f / height * y);

		return result;
	}

	private float pointToHue(float y) {

		final Rect rect = hueRect;

		float height = rect.height();

		if (y < rect.top) {
			y = 0f;
		} else if (y > rect.bottom) {
			y = height;
		} else {
			y = y - rect.top;
		}

		return 360f - (y * 360f / height);
	}

	private int pointToAlpha(int x) {

		final Rect rect = alphaRect;
		final int width = rect.width();

		if (x < rect.left) {
			x = 0;
		} else if (x > rect.right) {
			x = width;
		} else {
			x = x - rect.left;
		}

		return 0xff - (x * 0xff / width);

	}
	boolean update = false;
	public boolean wannaTouch() {
		return update;
	}
	@Override public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
			case MotionEvent.ACTION_DOWN:
				startTouchPoint = new Point((int) event.getX(), (int) event.getY());
				update = moveTrackersIfNeeded(event);
				break;
			case MotionEvent.ACTION_MOVE:
				update = moveTrackersIfNeeded(event);
				break;
			case MotionEvent.ACTION_UP:
				startTouchPoint = null;
				update = moveTrackersIfNeeded(event);
				break;
		}
		ProtectiveScrollView.dontIntercept=update;
		if (update) {
			if (onColorChangedListener != null) {
				onColorChangedListener.onColorChanged(Color.HSVToColor(alpha, new float[]{hue, sat, val}));
			}
			invalidate();
			//requestFocus();
			return true;
		}
		return true;
	}

	private boolean moveTrackersIfNeeded(MotionEvent event) {
		if (startTouchPoint == null) {
			return false;
		}

		boolean update = false;

		int startX = startTouchPoint.x;
		int startY = startTouchPoint.y;

		if (hueRect.contains(startX, startY)) {
			hue = pointToHue(event.getY());

			update = true;
		} else if (satValRect.contains(startX, startY)) {
			float[] result = pointToSatVal(event.getX(), event.getY());

			sat = result[0];
			val = result[1];

			update = true;
		} else if (alphaRect != null && alphaRect.contains(startX, startY)) {
			alpha = pointToAlpha((int) event.getX());

			update = true;
		}

		return update;
	}

	@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int finalWidth;
		int finalHeight;

		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		int heightMode = MeasureSpec.getMode(heightMeasureSpec);

		int widthAllowed = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
		int heightAllowed =
				MeasureSpec.getSize(heightMeasureSpec) - getPaddingBottom() - getPaddingTop();

		if (widthMode == MeasureSpec.EXACTLY || heightMode == MeasureSpec.EXACTLY) {
			//A exact value has been set in either direction, we need to stay within this size.

			if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
				//The with has been specified exactly, we need to adopt the height to fit.
				int h = (widthAllowed - panelSpacingPx - huePanelWidthPx);

				//if (true/*showAlphaPanel*/)
				h += panelSpacingPx + alphaPanelHeightPx;

				if (h > heightAllowed) {
					//We can't fit the view in this container, set the size to whatever was allowed.
					finalHeight = heightAllowed;
				} else {
					finalHeight = h;
				}

				finalWidth = widthAllowed;

			} else if (heightMode == MeasureSpec.EXACTLY && widthMode != MeasureSpec.EXACTLY) {
				//The height has been specified exactly, we need to stay within this height and adopt the width.

				int w = (heightAllowed + panelSpacingPx + huePanelWidthPx);

				//if (true/*showAlphaPanel*/)
				w -= (panelSpacingPx + alphaPanelHeightPx);

				if (w > widthAllowed) {
					//we can't fit within this container, set the size to whatever was allowed.
					finalWidth = widthAllowed;
				} else {
					finalWidth = w;
				}

				finalHeight = heightAllowed;

			} else {
				//If we get here the dev has set the width and height to exact sizes. For example match_parent or 300dp.
				//This will mean that the sat/val panel will not be square but it doesn't matter. It will work anyway.
				//In all other senarios our goal is to make that panel square.

				//We set the sizes to exactly what we were told.
				finalWidth = widthAllowed;
				finalHeight = heightAllowed;
			}

		} else {
			//If no exact size has been set we try to make our view as big as possible
			//within the allowed space.

			//Calculate the needed width to layout using max allowed height.
			int widthNeeded = (heightAllowed + panelSpacingPx + huePanelWidthPx);

			//Calculate the needed height to layout using max allowed width.
			int heightNeeded = (widthAllowed - panelSpacingPx - huePanelWidthPx);

			//if (true/*showAlphaPanel*/)
			widthNeeded -= (panelSpacingPx + alphaPanelHeightPx);
			heightNeeded += panelSpacingPx + alphaPanelHeightPx;

			boolean widthOk = false;
			boolean heightOk = false;

			if (widthNeeded <= widthAllowed) {
				widthOk = true;
			}

			if (heightNeeded <= heightAllowed) {
				heightOk = true;
			}

			if (widthOk && heightOk) {
				finalWidth = widthAllowed;
				finalHeight = heightNeeded;
			} else if (!heightOk && widthOk) {
				finalHeight = heightAllowed;
				finalWidth = widthNeeded;
			} else if (!widthOk && heightOk) {
				finalHeight = heightNeeded;
				finalWidth = widthAllowed;
			} else {
				finalHeight = heightAllowed;
				finalWidth = widthAllowed;
			}

		}

		setMeasuredDimension(finalWidth + getPaddingLeft() + getPaddingRight(),
				finalHeight + getPaddingTop() + getPaddingBottom());
	}

	private int getPreferredWidth() {
		//Our preferred width and height is 200dp for the square sat / val rectangle.
		int width = DrawingUtils.dpToPx(getContext(), 200);

		return (width + huePanelWidthPx + panelSpacingPx);
	}

	private int getPreferredHeight() {
		int height = DrawingUtils.dpToPx(getContext(), 200);

		//if (true/*showAlphaPanel*/)
		height += panelSpacingPx + alphaPanelHeightPx;

		return height;
	}

	@Override public int getPaddingTop() {
		return Math.max(super.getPaddingTop(), mRequiredPadding);
	}

	@Override public int getPaddingBottom() {
		return Math.max(super.getPaddingBottom(), mRequiredPadding);
	}

	@Override public int getPaddingLeft() {
		return Math.max(super.getPaddingLeft(), mRequiredPadding);
	}

	@Override public int getPaddingRight() {
		return Math.max(super.getPaddingRight(), mRequiredPadding);
	}

	@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);

		drawingRect = new Rect();
		drawingRect.left = getPaddingLeft();
		drawingRect.right = w - getPaddingRight();
		drawingRect.top = getPaddingTop();
		drawingRect.bottom = h - getPaddingBottom();

		//The need to be recreated because they depend on the size of the view.
		valShader = null;
		satShader = null;
		alphaShader = null;

		// Clear those bitmap caches since the size may have changed.
		satValBackgroundCache = null;
		hueBackgroundCache = null;

		setUpSatValRect();
		setUpHueRect();
		setUpAlphaRect();
	}

	private void setUpSatValRect() {
		//Calculate the size for the big color rectangle.
		final Rect dRect = drawingRect;

		int left = dRect.left + BORDER_WIDTH_PX;
		int top = dRect.top + BORDER_WIDTH_PX;
		int bottom = dRect.bottom - BORDER_WIDTH_PX;
		int right = dRect.right - BORDER_WIDTH_PX - panelSpacingPx - huePanelWidthPx;

		//if (true/*showAlphaPanel*/)
		bottom -= (alphaPanelHeightPx + panelSpacingPx);

		satValRect = new Rect(left, top, right, bottom);
	}

	private void setUpHueRect() {
		//Calculate the size for the hue slider on the left.
		final Rect dRect = drawingRect;

		int left = dRect.right - huePanelWidthPx + BORDER_WIDTH_PX;
		int top = dRect.top + BORDER_WIDTH_PX;
		int bottom = dRect.bottom - BORDER_WIDTH_PX - (panelSpacingPx + alphaPanelHeightPx);
				//(true/*showAlphaPanel*/ ? (panelSpacingPx + alphaPanelHeightPx) : 0);
		int right = dRect.right - BORDER_WIDTH_PX;

		hueRect = new Rect(left, top, right, bottom);
	}

	private void setUpAlphaRect() {
		//if (!true/*showAlphaPanel*/) return;
		final Rect dRect = drawingRect;

		int left = dRect.left + BORDER_WIDTH_PX;
		int top = dRect.bottom - alphaPanelHeightPx + BORDER_WIDTH_PX;
		int bottom = dRect.bottom - BORDER_WIDTH_PX;
		int right = dRect.right - BORDER_WIDTH_PX;

		alphaRect = new Rect(left, top, right, bottom);

		alphaPatternDrawable = new AlphaPatternDrawable(DrawingUtils.dpToPx(getContext(), 4));
		alphaPatternDrawable.setBounds(Math.round(alphaRect.left), Math
				.round(alphaRect.top), Math.round(alphaRect.right), Math
				.round(alphaRect.bottom));
	}

	/**
	 * Set a OnColorChangedListener to get notified when the color
	 * selected by the user has changed.
	 *
	 * @param listener
	 *     the listener
	 */
	public void setOnColorChangedListener(OnColorChangedListener listener) {
		onColorChangedListener = listener;
	}

	/**
	 * Get the current color this view is showing.
	 *
	 * @return the current color.
	 */
	public int getColor() {
		return Color.HSVToColor(alpha, new float[]{hue, sat, val});
	}

	/**
	 * Set the color the view should show.
	 *
	 * @param color
	 *     The color that should be selected. #argb
	 */
	public void setColor(int color) {
		setColor(color, false);
	}

	/**
	 * Set the color this view should show.
	 *
	 * @param color
	 *     The color that should be selected. #argb
	 * @param callback
	 *     If you want to get a callback to your OnColorChangedListener.
	 */
	public void setColor(int color, boolean callback) {
		int alpha = Color.alpha(color);
		int red = Color.red(color);
		int blue = Color.blue(color);
		int green = Color.green(color);

		float[] hsv = new float[3];

		Color.RGBToHSV(red, green, blue, hsv);

		this.alpha = alpha;
		hue = hsv[0];
		sat = hsv[1];
		val = hsv[2];

		if (callback && onColorChangedListener != null) {
			onColorChangedListener
					.onColorChanged(Color.HSVToColor(this.alpha, new float[]{hue, sat, val}));
		}

		invalidate();
	}

	/**
	 * Set if the user is allowed to adjust the alpha panel. Default is false.
	 * If it is set to false no alpha will be set.
	 *
	 * @param visible
	 *     {@code true} to show the alpha slider
	 */
	public void setAlphaSliderVisible(boolean visible) {
		if (!visible) {//true/*showAlphaPanel*/ !=
			/*
			 * Force recreation.
			 */
			valShader = null;
			satShader = null;
			alphaShader = null;
			hueBackgroundCache = null;
			satValBackgroundCache = null;

			requestLayout();
		}

	}

	/**
	 * Set the color of the tracker slider on the hue and alpha panel.
	 *
	 * @param color
	 *     a color value
	 */
	public void setSliderTrackerColor(int color) {
		sliderTrackerColor = color;
		hueAlphaTrackerPaint.setColor(sliderTrackerColor);
		invalidate();
	}

	/**
	 * Get color of the tracker slider on the hue and alpha panel.
	 *
	 * @return the color value
	 */
	public int getSliderTrackerColor() {
		return sliderTrackerColor;
	}

	/**
	 * Set the color of the border surrounding all panels.
	 *
	 * @param color
	 *     a color value
	 */
	public void setBorderColor(int color) {
		borderColor = color;
		invalidate();
	}

	/**
	 * Get the color of the border surrounding all panels.
	 */
	public int getBorderColor() {
		return borderColor;
	}

	private class BitmapCache {

		public Canvas canvas;
		public Bitmap bitmap;
		public float value;
	}

	public interface OnColorChangedListener {
		void onColorChanged(int newColor);
	}



}
